package com.xplay.xpocker.socket;

import com.xplay.xpocker.exception.ServiceAssertion;
import com.xplay.xpocker.exception.ServiceRuntimeException;
import com.xplay.xpocker.exception.StatusCodeEnum;
import com.xplay.xpocker.observer.ChannelHandlerManager;
import com.xplay.xpocker.util.*;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;

@Slf4j
public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
    public static final String CLOSE_MESSAGE = "===xBL6LzLD^eQnKLwtkKgCM9^wbTKmYR2C===";

    private WebSocketServerHandshaker handshake;

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
    }

    /**
     * 加入心跳检查机制   当用户不再发送心跳的时候 也将当前用户移除
     *
     * @param ctx
     * @param evt
     * @throws Exception
     */

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.READER_IDLE) {
                ChannelHandlerManager.getInstance().userOOEvent(ctx, false);
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        //首次请求之后先进行握手，通过http请求来实现
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof PingWebSocketFrame) {
            // 如果是 ping 消息，则回应一个 pong 消息
            PingWebSocketFrame pingWebSocketFrame = (PingWebSocketFrame) msg;
            ctx.writeAndFlush(new PongWebSocketFrame(pingWebSocketFrame.content().retain()));
        } else if (msg instanceof WebSocketFrame) {
            handleWebSocketRequest(ctx, (WebSocketFrame) msg);
        }
    }

    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
        StringBuilder logBuilder = new StringBuilder("\n");
        try {
            Channel channel = ctx.channel();
            //解码http失败返回
            if (!request.decoderResult().isSuccess()) {
                sendResponse(ctx, request, new DefaultFullHttpResponse(request.protocolVersion(), HttpResponseStatus.BAD_REQUEST, ctx.alloc().buffer()));
                return;
            }


            if (!HttpMethod.GET.equals(request.method())) {
                sendResponse(ctx, request, new DefaultFullHttpResponse(request.protocolVersion(), HttpResponseStatus.FORBIDDEN, ctx.alloc().buffer()));
                return;
            }
            InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress();
            String remoteIp = remoteAddress.getAddress().getHostAddress();
            logBuilder.append("[用户地址]:").append(remoteIp).append("\n");
            HttpHeaders headers = request.headers();
            String token = headers.get("sec-websocket-protocol");
            logBuilder.append("[认证信息]:").append(token).append("\n");
            QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri());
            Map<String, List<String>> parameters = queryStringDecoder.parameters();
            if (parameters == null || parameters.size() == 0) {
                sendResponse(ctx, request, new DefaultFullHttpResponse(request.protocolVersion(), HttpResponseStatus.FORBIDDEN, ctx.alloc().buffer()));
                return;
            }
            // 当 握手成功的时候  调用策略 模式的连接器
            String roomCode = parameters.get(ChannelHandlerManager.ROOM_CODE_KEY).get(0);
            logBuilder.append("[连接房间]:").append(roomCode).append("\n");
            ServiceAssertion.isTrue(!JwtUtils.validateToken(token), StatusCodeEnum.ROOM_NOT_FOUND);
            ServiceAssertion.isEmpty(roomCode, StatusCodeEnum.ROOM_NOT_FOUND);
            Object userId = JwtUtils.getAllClaimsFromToken(token).get(JwtUtils.UID);
            logBuilder.append("[连接用户]:").append(userId).append("\n");
            ctx.channel().attr(AttributeKey.valueOf(ChannelHandlerManager.USER_CHANNEL_KEY)).set(userId);
            ctx.channel().attr(AttributeKey.valueOf(ChannelHandlerManager.ROOM_CODE_KEY)).set(roomCode);
            //参数分别是ws地址，子协议，是否扩展，最大frame长度
            WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory(getWebSocketLocation(request), token, true, 5 * 1024 * 1024);
            handshake = factory.newHandshaker(request);
            if (handshake == null) {
                WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
            } else {
                handshake.handshake(ctx.channel(), request);
            }
            //用户连接成功
            logBuilder.append("[连接状态]:").append("SUCCESS").append("\n");
            // 将用户状态设置为在线
            ChannelHandlerManager.getInstance().userOOEvent(ctx, true);
        } catch (ServiceRuntimeException e) {
            logBuilder.append("[连接状态]:").append("FAIL").append("\n");
            logBuilder.append("[异常信息]:").append(e.getMessage()).append("\n");
            e.printStackTrace();
            ctx.channel().close();
        } finally {
            log.debug(logBuilder.toString());
        }
    }


    //SSL支持采用wss://
    private String getWebSocketLocation(FullHttpRequest request) {
        String location = request.headers().get(HttpHeaderNames.HOST) + "/websocket";
        return "ws://" + location;
    }

    private void handleWebSocketRequest(ChannelHandlerContext ctx, WebSocketFrame frame) {
        //关闭
        if (frame instanceof CloseWebSocketFrame) {
            handshake.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
        //握手 PING/PONG
        if (frame instanceof PingWebSocketFrame) {
            ctx.write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        //文本接收和发送
        if (frame instanceof TextWebSocketFrame) {
            return;
        }
        if (frame instanceof BinaryWebSocketFrame) {
            ctx.write(frame.retain());
        }
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        // 当连接主动断开的时候 找到房间  将用户的状态改为离线  并发送 离线消息给每一个用户
        // 因为网络原因造成的连接断开 这里是无法监听的   所以需要客户端定时发送心跳给服务器
        ChannelHandlerManager.getInstance().userOOEvent(ctx, false);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    private void sendResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse resp) {
        HttpResponseStatus status = resp.status();
        if (status != HttpResponseStatus.OK) {
            ByteBufUtil.writeUtf8(resp.content(), status.toString());
            HttpUtil.setContentLength(req, resp.content().readableBytes());
        }
        boolean keepAlive = HttpUtil.isKeepAlive(req) && status == HttpResponseStatus.OK;
        HttpUtil.setKeepAlive(req, keepAlive);
        ChannelFuture future = ctx.write(resp);
        if (!keepAlive) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        log.debug("----------channel 异常------------");
        ctx.close();
    }
}

